package com.example.config;

import com.example.entity.User;
import com.example.mapper.PermissionMapper;
import com.example.mapper.RoleMapper;
import com.example.mapper.UserMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * 1、自定义Realm：创建类继承 `AuthorizingRealm`
 * 2、重写doGetAuthorizationInfo和doGetAuthenticationInfo方法
 * 3、重写getName方法返回当前realm的自定义名称（多个realm）
 */
public class MyRealm extends AuthorizingRealm {

    /**
     * Shiro 会自动为缓存生成默认名称：
     * 认证缓存默认名：<realm name> + "_authenticationCache" 例如：myRealm_authenticationCache
     * 授权缓存默认名：<realm name> + "_authorizationCache" 例如：myRealm_authorizationCache
     * 这些缓存名称如果在 ehcache.xml 中没有显式定义，则会使用 <defaultCache> 配置。
     * <p></p>
     * 原因                               说明
     * ✅ Shiro 默认启用缓存               AuthorizingRealm 默认开启认证/授权缓存
     * ✅ 自动命名缓存                     使用默认生成的缓存名称（如 myRealm_authenticationCache）
     * ✅ Ehcache 有 <defaultCache>      没有匹配的缓存名时使用默认配置
     * ⚠️ 不推荐省略配置                   显式配置更清晰、可控性更强，避免潜在问题
     */
    public MyRealm() {
        /**
         * 开启缓存
         * 启用 Shiro 的认证缓存（authenticationCache）和授权缓存（authorizationCache）
         */
        setCachingEnabled(true);
        /**
         * 开启认证缓存
         */
        setAuthenticationCachingEnabled(true);
        setAuthenticationCacheName("authenticationCache");
        /**
         * 开启授权缓存
         */
        setAuthorizationCachingEnabled(true);
        setAuthorizationCacheName("authorizationCache");
    }

    /**
     * 先认证再授权
     */

    /**
     * 根据用户名查询用户信息
     */
    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户名查询当前用户的角色列表
     */
    @Autowired
    private RoleMapper roleMapper;

    /**
     * 根据用户名查询当前用户的权限列表
     */
    @Autowired
    private PermissionMapper permissionMapper;

    /**
     * 获取授权数据（将当前用户的角色以及权限信息查询出来）
     * 完成权限、角色查询
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /**
         * 获取用户的用户名
         * 当前能够获取到什么信息取决于doGetAuthenticationInfo方法中返回SimpleAuthenticationInfo的第一个参数信息（一般为用户名）
         */
        String username = (String) principalCollection.iterator().next();
        /**
         * 根据用户名查询当前用户的角色列表
         */
        Set<String> roleNames = roleMapper.queryRoleNamesByUsername(username);
        /**
         * 根据用户名查询当前用户的权限列表
         */
        Set<String> permissions = permissionMapper.queryPermissionsByUsername(username);

        /**
         * Shiro缓存测试
         */
        System.out.println("==查询客户信息==");

        /**
         * 赋予角色和权限
         */
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roleNames); // 角色
        authorizationInfo.setStringPermissions(permissions); // 权限
        return authorizationInfo;
    }

    /**
     * 获取认证数据（从数据库查询用户的正确数据）
     * 完成登录认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /**
         * 参数authenticationToken传递的是subject.login(token)
         */
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        /**
         * 从token中获取用户名
         */
        String username = token.getUsername();
        /**
         * 根据用户名从数据库查询当前用户的安全数据
         */
        User user = userMapper.queryUserByUsername(username);
        /**
         * 判断用户是否存在
         */
        if (user == null) {
            throw new UnknownAccountException("用户不存在");
        }
        /**
         * 将信息封装为AuthenticationInfo进行返回
         * 密码匹配交给 Shiro 完成
         */
//        return new SimpleAuthenticationInfo(
//                username, // principal（当前用户名）
//                user.getPassword(), // credentials（数据库查询的密码）
//                getName() // realm名称（当前使用的realm）
//        );
        /**
         * 如果数据库中用户的密码是加盐存储：则Realm在返回认证数据时需要返回盐
         */
        return new SimpleAuthenticationInfo(
                username,
                /**
                 * 从数据库中查到的加密后的密码
                 */
                user.getPassword(),
                /**
                 * 从数据库中查到的盐
                 * 指定加盐的盐：要与用户注册时密码加盐的盐一致
                 */
                ByteSource.Util.bytes(user.getPasswordSalt()),
                /**
                 * 使用的Realm名称
                 */
                getName()
        );
    }

    /**
     * 身份标识（多个realm）
     */
    @Override
    public String getName() {
        return "myRealm";
    }

}
